home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
NeXTSTEP 3.1 (Developer) [x86]
/
NeXT Step 3.1 Intel dev.cdr.dmg
/
NextDeveloper
/
Examples
/
AppKit
/
Graph
/
LineGraph.m
< prev
next >
Wrap
Text File
|
1992-06-21
|
8KB
|
280 lines
/*
LineGraph.m
LineGraph is a simple view which plots a set of points as a connected line.
LineGraph draws the graph using a userpath. It retains the two arrays
that make up the userpath: the coordinates of the userpath and the
userpath operators (e.g., dps_lineto).
You may freely copy, distribute, and reuse the code in this example.
NeXT disclaims any warranty of any kind, expressed or implied, as to its
fitness for any particular use.
*/
#import "Graph.h"
static void drawAxes(float x1, float y1, float x2, float y2);
static void testBounds(float *min, float *max, float *points, int num);
#define POS_INFINITY (1.0/0.0)
#define NEG_INFINITY (-1.0/0.0)
#define IS_NAN(x) ((x)!=(x)) /* IEEE test for NAN (not a number) */
#define IS_STRANGE(x) (IS_NAN(x) || x == POS_INFINITY || x == NEG_INFINITY)
@implementation LineGraph
- initFrame:(NXRect *)aRect {
[super initFrame:aRect];
lineGray = NX_WHITE;
backgroundGray = NX_BLACK;
return self;
}
- free {
NXZoneFree([self zone], points);
NXZoneFree([self zone], ops);
return [super free];
}
- setPoints:(int)num x:(float *)x y:(float *)y
minX:(float)minX minY:(float)minY maxX:(float)maxX maxY:(float)maxY {
float *f;
int i;
char *op;
NXZone *zone;
int segPoints;
/*
* We copy these points into our own an array that we later use when
* we draw them as a userpath. This array has the x and y values
* interleaved. We also generate an array of userpath operators that we
* will use to draw the userpath. These operators are just a moveto to
* get to the start of the line, and then lineto's connecting all the points
* from there on.
*
* We also filter out the troublesome floating point values of positive
* infinity, negative infinity and NAN ("not a number"). When we encounter
* these values, we skip that particular point and remember to start a new
* segment of the graph when we see the next well behaved value.
*/
zone = [self zone];
NXZoneFree(zone, points);
NXZoneFree(zone, ops);
numPoints = num;
points = NXZoneMalloc(zone, 2 * num * sizeof(float));
ops = NXZoneMalloc(zone, num * sizeof(char));
numPoints = 0;
segPoints = 0;
for (f = points, op = ops, i = num; i--; ) {
if (IS_STRANGE(*x) || IS_STRANGE(*y)) {
if (segPoints == 1) {
/*
* This is a the case of a well behaved point with weird values
* on either side. In this case we just make a tiny line
* segment of this point to itself to.
*/
*f++ = f[-2];
*f++ = f[-2];
*op++ = dps_lineto;
numPoints++;
}
segPoints = 0;
x++;
y++;
} else {
*f++ = *x++;
*f++ = *y++;
if (segPoints == 0)
*op++ = dps_moveto;
else
*op++ = dps_lineto;
segPoints++;
numPoints++;
}
}
if (numPoints > 0) {
testBounds(&minX, &maxX, points, numPoints);
bbox[0] = minX;
bbox[2] = maxX;
testBounds(&minY, &maxY, points+1, numPoints);
bbox[1] = minY;
bbox[3] = maxY;
} else {
bbox[0] = bbox[1] = -1;
bbox[2] = bbox[3] = 1;
}
return self;
}
- sizeTo:(NXCoord)width :(NXCoord)height {
NXPoint center;
/*
* We override sizeto so that after we're resized the point at the center
* of the view remains the same. We just note the center, pass the message
* up to the super class, and then reset the center to what it was.
*/
center.x = NX_X(&bounds) + NX_WIDTH(&bounds) / 2;
center.y = NX_Y(&bounds) + NX_HEIGHT(&bounds) / 2;
[super sizeTo:width :height];
[self setDrawOrigin:center.x - NX_WIDTH(&bounds) / 2
:center.y - NX_HEIGHT(&bounds) / 2];
return self;
}
- scaleToFit {
float scale;
float bbWidth = bbox[2] - bbox[0];
float bbHeight = bbox[3] - bbox[1];
/*
* First we figure out how much we need to scale so that our current graph
* will just fit within the view. We do this by taking the ratio of the
* the view's current size to the bounding box of the graph. We also add
* a little fudge factor of 0.95 so the graph will have a little border
* space around it.
*/
if (bbWidth == 0 && bbHeight == 0)
scale = 10;
else if (bbWidth == 0)
scale = NX_HEIGHT(&frame) / bbHeight * 0.95;
else if (bbHeight == 0)
scale = NX_WIDTH(&frame) / bbWidth * 0.95;
else
scale = MIN(NX_WIDTH(&frame) / bbWidth,
NX_HEIGHT(&frame) / bbHeight) * 0.95;
/* scale the size of the view */
[self setDrawSize:NX_WIDTH(&frame) / scale :NX_HEIGHT(&frame) / scale];
/*
* translate the view so the graph's bounding box is in the lower left corner
*/
[self setDrawOrigin:bbox[0] - (NX_WIDTH(&bounds) - bbWidth) / 2
:bbox[1] - (NX_HEIGHT(&bounds) - bbHeight) / 2];
return self;
}
- zoom:(float)scale {
NXPoint center;
/* remember the center to we can reset it after the scaling */
center.x = NX_X(&bounds) + NX_WIDTH(&bounds) / 2;
center.y = NX_Y(&bounds) + NX_HEIGHT(&bounds) / 2;
/* scale the view to its new size */
[self setDrawSize:NX_WIDTH(&bounds) / scale :NX_HEIGHT(&bounds) / scale];
/* reset the center point */
[self setDrawOrigin:center.x - NX_WIDTH(&bounds) / 2
:center.y - NX_HEIGHT(&bounds) / 2];
return self;
}
/*
* In both the drawSelf method and the drawAxes function, we use a little
* trick to get the right line width regardless of how much we are zoomed
* in. If we are drawing on the screen, we just draw the lines with zero
* width. This the fastest way to draw lines, and ensures they will always
* one pixel wide. When drawing on the printer, we do the trick of
* setting the matrix to the default matrix after the path has been made
* but before it is stroked. Since the line width is actually used when
* the path is stroked, this makes the line width be the same regardless of
* what scale we were at when we stroked the path. The purpose of this is
* to make the line width independent of how far in or our we are zoomed.
*/
- drawSelf:(const NXRect *)rects :(int)rectCount {
PSsetgray(backgroundGray);
NXRectFill(&bounds);
drawAxes(NX_X(&bounds), NX_Y(&bounds), NX_MAXX(&bounds), NX_MAXY(&bounds));
if (points && numPoints > 0) {
PSnewpath();
PSsetlinewidth(NXDrawingStatus == NX_DRAWING ? 0.0 : 1.0);
PSsetgray(lineGray);
if (NXDrawingStatus == NX_DRAWING)
/* stroke the userpath */
DPSDoUserPath(points, numPoints * 2, dps_float, ops, numPoints,
bbox, dps_ustroke);
else {
/* append the userpath to the current path, but dont stroke yet */
DPSDoUserPath(points, numPoints * 2, dps_float, ops, numPoints,
bbox, dps_uappend);
/* trick to ensure line width is independent of zoom */
PSgsave();
PSmatrix();
PSdefaultmatrix();
PSsetmatrix();
PSstroke();
PSgrestore();
}
}
return self;
}
/*
* Simple line drawer, used to draw the axes of the graph in the current
* color and line width.
*/
static void drawAxes(float x1, float y1, float x2, float y2) {
PSsetlinewidth(NXDrawingStatus == NX_DRAWING ? 0.0 : 0.4);
PSsetgray(NX_DKGRAY);
PSnewpath();
PSmoveto(0.0, y1);
PSlineto(0.0, y2);
PSmoveto(x1, 0.0);
PSlineto(x2, 0.0);
if (NXDrawingStatus != NX_DRAWING) {
/* trick to ensure line width is independent of zoom */
PSgsave();
PSmatrix();
PSdefaultmatrix();
PSsetmatrix();
PSstroke();
PSgrestore();
} else
PSstroke();
}
- setLineGray:(float)gray {
lineGray = gray;
return self;
}
- setBackgroundGray:(float)gray {
backgroundGray = gray;
return self;
}
- (float)lineGray {
return lineGray;
}
- (float)backgroundGray {
return backgroundGray;
}
/*
* Tests the bounds for NAN or infinite values. If there are such values,
* we recalc the bounds with the given points. Since these points are
* the interleaved x-y pairs for the userpath, we skip every other value,
* since we want to only consider x's or y's.
*/
static void testBounds(float *min, float *max, float *points, int num) {
if (IS_STRANGE(*min) || IS_STRANGE(*max)) {
*min = *max = *points;
while (--num) {
points += 2;
if (*points > *max)
*max = *points;
else if (*points < *min)
*min = *points;
}
}
}
@end